/*
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.smartandroid.sa.json;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;

import com.smartandroid.sa.json.internal.$Gson$Types;

/**
 * List of all the default type adapters ({@link JsonSerializer}s,
 * {@link JsonDeserializer}s, and {@link InstanceCreator}s.
 * 
 * @author Inderjeet Singh
 * @author Joel Leitch
 */
final class DefaultTypeAdapters {

	private static final DefaultDateTypeAdapter DATE_TYPE_ADAPTER = new DefaultDateTypeAdapter();
	private static final DefaultJavaSqlDateTypeAdapter JAVA_SQL_DATE_TYPE_ADAPTER = new DefaultJavaSqlDateTypeAdapter();
	private static final DefaultTimeTypeAdapter TIME_TYPE_ADAPTER = new DefaultTimeTypeAdapter();
	private static final DefaultTimestampDeserializer TIMESTAMP_DESERIALIZER = new DefaultTimestampDeserializer();

	@SuppressWarnings("unchecked")
	private static final EnumTypeAdapter ENUM_TYPE_ADAPTER = new EnumTypeAdapter();
	private static final UrlTypeAdapter URL_TYPE_ADAPTER = new UrlTypeAdapter();
	private static final UriTypeAdapter URI_TYPE_ADAPTER = new UriTypeAdapter();
	private static final UuidTypeAdapter UUUID_TYPE_ADAPTER = new UuidTypeAdapter();
	private static final LocaleTypeAdapter LOCALE_TYPE_ADAPTER = new LocaleTypeAdapter();
	private static final BitSetTypeAdapter BIT_SET_ADAPTER = new BitSetTypeAdapter();
	private static final DefaultInetAddressAdapter INET_ADDRESS_ADAPTER = new DefaultInetAddressAdapter();
	private static final CollectionTypeAdapter COLLECTION_TYPE_ADAPTER = new CollectionTypeAdapter();
	private static final MapTypeAdapter MAP_TYPE_ADAPTER = new MapTypeAdapter();
	private static final BigDecimalTypeAdapter BIG_DECIMAL_TYPE_ADAPTER = new BigDecimalTypeAdapter();
	private static final BigIntegerTypeAdapter BIG_INTEGER_TYPE_ADAPTER = new BigIntegerTypeAdapter();

	private static final BooleanTypeAdapter BOOLEAN_TYPE_ADAPTER = new BooleanTypeAdapter();
	private static final ByteTypeAdapter BYTE_TYPE_ADAPTER = new ByteTypeAdapter();
	private static final CharacterTypeAdapter CHARACTER_TYPE_ADAPTER = new CharacterTypeAdapter();
	private static final DoubleDeserializer DOUBLE_TYPE_ADAPTER = new DoubleDeserializer();
	private static final FloatDeserializer FLOAT_TYPE_ADAPTER = new FloatDeserializer();
	private static final IntegerTypeAdapter INTEGER_TYPE_ADAPTER = new IntegerTypeAdapter();
	private static final LongDeserializer LONG_DESERIALIZER = new LongDeserializer();
	private static final NumberTypeAdapter NUMBER_TYPE_ADAPTER = new NumberTypeAdapter();
	private static final ShortTypeAdapter SHORT_TYPE_ADAPTER = new ShortTypeAdapter();
	private static final StringTypeAdapter STRING_TYPE_ADAPTER = new StringTypeAdapter();
	private static final StringBuilderTypeAdapter STRING_BUILDER_TYPE_ADAPTER = new StringBuilderTypeAdapter();
	private static final StringBufferTypeAdapter STRING_BUFFER_TYPE_ADAPTER = new StringBufferTypeAdapter();

	private static final GregorianCalendarTypeAdapter GREGORIAN_CALENDAR_TYPE_ADAPTER = new GregorianCalendarTypeAdapter();

	// The constants DEFAULT_SERIALIZERS, DEFAULT_DESERIALIZERS, and
	// DEFAULT_INSTANCE_CREATORS
	// must be defined after the constants for the type adapters. Otherwise, the
	// type adapter
	// constants will appear as nulls.
	private static final ParameterizedTypeHandlerMap<JsonSerializer<?>> DEFAULT_SERIALIZERS = createDefaultSerializers();
	static final ParameterizedTypeHandlerMap<JsonSerializer<?>> DEFAULT_HIERARCHY_SERIALIZERS = createDefaultHierarchySerializers();
	private static final ParameterizedTypeHandlerMap<JsonDeserializer<?>> DEFAULT_DESERIALIZERS = createDefaultDeserializers();
	static final ParameterizedTypeHandlerMap<JsonDeserializer<?>> DEFAULT_HIERARCHY_DESERIALIZERS = createDefaultHierarchyDeserializers();
	private static final ParameterizedTypeHandlerMap<InstanceCreator<?>> DEFAULT_INSTANCE_CREATORS = createDefaultInstanceCreators();

	private static ParameterizedTypeHandlerMap<JsonSerializer<?>> createDefaultSerializers() {
		ParameterizedTypeHandlerMap<JsonSerializer<?>> map = new ParameterizedTypeHandlerMap<JsonSerializer<?>>();

		map.register(URL.class, URL_TYPE_ADAPTER, true);
		map.register(URI.class, URI_TYPE_ADAPTER, true);
		map.register(UUID.class, UUUID_TYPE_ADAPTER, true);
		map.register(Locale.class, LOCALE_TYPE_ADAPTER, true);
		map.register(Date.class, DATE_TYPE_ADAPTER, true);
		map.register(java.sql.Date.class, JAVA_SQL_DATE_TYPE_ADAPTER, true);
		map.register(Timestamp.class, DATE_TYPE_ADAPTER, true);
		map.register(Time.class, TIME_TYPE_ADAPTER, true);
		map.register(Calendar.class, GREGORIAN_CALENDAR_TYPE_ADAPTER, true);
		map.register(GregorianCalendar.class, GREGORIAN_CALENDAR_TYPE_ADAPTER,
				true);
		map.register(BigDecimal.class, BIG_DECIMAL_TYPE_ADAPTER, true);
		map.register(BigInteger.class, BIG_INTEGER_TYPE_ADAPTER, true);
		map.register(BitSet.class, BIT_SET_ADAPTER, true);

		// Add primitive serializers
		map.register(Boolean.class, BOOLEAN_TYPE_ADAPTER, true);
		map.register(boolean.class, BOOLEAN_TYPE_ADAPTER, true);
		map.register(Byte.class, BYTE_TYPE_ADAPTER, true);
		map.register(byte.class, BYTE_TYPE_ADAPTER, true);
		map.register(Character.class, CHARACTER_TYPE_ADAPTER, true);
		map.register(char.class, CHARACTER_TYPE_ADAPTER, true);
		map.register(Integer.class, INTEGER_TYPE_ADAPTER, true);
		map.register(int.class, INTEGER_TYPE_ADAPTER, true);
		map.register(Number.class, NUMBER_TYPE_ADAPTER, true);
		map.register(Short.class, SHORT_TYPE_ADAPTER, true);
		map.register(short.class, SHORT_TYPE_ADAPTER, true);
		map.register(String.class, STRING_TYPE_ADAPTER, true);
		map.register(StringBuilder.class, STRING_BUILDER_TYPE_ADAPTER, true);
		map.register(StringBuffer.class, STRING_BUFFER_TYPE_ADAPTER, true);

		map.makeUnmodifiable();
		return map;
	}

	private static ParameterizedTypeHandlerMap<JsonSerializer<?>> createDefaultHierarchySerializers() {
		ParameterizedTypeHandlerMap<JsonSerializer<?>> map = new ParameterizedTypeHandlerMap<JsonSerializer<?>>();
		map.registerForTypeHierarchy(Enum.class, ENUM_TYPE_ADAPTER, true);
		map.registerForTypeHierarchy(InetAddress.class, INET_ADDRESS_ADAPTER,
				true);
		map.registerForTypeHierarchy(Collection.class, COLLECTION_TYPE_ADAPTER,
				true);
		map.registerForTypeHierarchy(Map.class, MAP_TYPE_ADAPTER, true);
		map.makeUnmodifiable();
		return map;
	}

	private static ParameterizedTypeHandlerMap<JsonDeserializer<?>> createDefaultDeserializers() {
		ParameterizedTypeHandlerMap<JsonDeserializer<?>> map = new ParameterizedTypeHandlerMap<JsonDeserializer<?>>();
		map.register(URL.class, wrapDeserializer(URL_TYPE_ADAPTER), true);
		map.register(URI.class, wrapDeserializer(URI_TYPE_ADAPTER), true);
		map.register(UUID.class, wrapDeserializer(UUUID_TYPE_ADAPTER), true);
		map.register(Locale.class, wrapDeserializer(LOCALE_TYPE_ADAPTER), true);
		map.register(Date.class, wrapDeserializer(DATE_TYPE_ADAPTER), true);
		map.register(java.sql.Date.class,
				wrapDeserializer(JAVA_SQL_DATE_TYPE_ADAPTER), true);
		map.register(Timestamp.class, wrapDeserializer(TIMESTAMP_DESERIALIZER),
				true);
		map.register(Time.class, wrapDeserializer(TIME_TYPE_ADAPTER), true);
		map.register(Calendar.class, GREGORIAN_CALENDAR_TYPE_ADAPTER, true);
		map.register(GregorianCalendar.class, GREGORIAN_CALENDAR_TYPE_ADAPTER,
				true);
		map.register(BigDecimal.class, BIG_DECIMAL_TYPE_ADAPTER, true);
		map.register(BigInteger.class, BIG_INTEGER_TYPE_ADAPTER, true);
		map.register(BitSet.class, BIT_SET_ADAPTER, true);

		// Add primitive deserializers
		map.register(Boolean.class, BOOLEAN_TYPE_ADAPTER, true);
		map.register(boolean.class, BOOLEAN_TYPE_ADAPTER, true);
		map.register(Byte.class, BYTE_TYPE_ADAPTER, true);
		map.register(byte.class, BYTE_TYPE_ADAPTER, true);
		map.register(Character.class, wrapDeserializer(CHARACTER_TYPE_ADAPTER),
				true);
		map.register(char.class, wrapDeserializer(CHARACTER_TYPE_ADAPTER), true);
		map.register(Double.class, DOUBLE_TYPE_ADAPTER, true);
		map.register(double.class, DOUBLE_TYPE_ADAPTER, true);
		map.register(Float.class, FLOAT_TYPE_ADAPTER, true);
		map.register(float.class, FLOAT_TYPE_ADAPTER, true);
		map.register(Integer.class, INTEGER_TYPE_ADAPTER, true);
		map.register(int.class, INTEGER_TYPE_ADAPTER, true);
		map.register(Long.class, LONG_DESERIALIZER, true);
		map.register(long.class, LONG_DESERIALIZER, true);
		map.register(Number.class, NUMBER_TYPE_ADAPTER, true);
		map.register(Short.class, SHORT_TYPE_ADAPTER, true);
		map.register(short.class, SHORT_TYPE_ADAPTER, true);
		map.register(String.class, wrapDeserializer(STRING_TYPE_ADAPTER), true);
		map.register(StringBuilder.class,
				wrapDeserializer(STRING_BUILDER_TYPE_ADAPTER), true);
		map.register(StringBuffer.class,
				wrapDeserializer(STRING_BUFFER_TYPE_ADAPTER), true);

		map.makeUnmodifiable();
		return map;
	}

	private static ParameterizedTypeHandlerMap<JsonDeserializer<?>> createDefaultHierarchyDeserializers() {
		ParameterizedTypeHandlerMap<JsonDeserializer<?>> map = new ParameterizedTypeHandlerMap<JsonDeserializer<?>>();
		map.registerForTypeHierarchy(Enum.class,
				wrapDeserializer(ENUM_TYPE_ADAPTER), true);
		map.registerForTypeHierarchy(InetAddress.class,
				wrapDeserializer(INET_ADDRESS_ADAPTER), true);
		map.registerForTypeHierarchy(Collection.class,
				wrapDeserializer(COLLECTION_TYPE_ADAPTER), true);
		map.registerForTypeHierarchy(Map.class,
				wrapDeserializer(MAP_TYPE_ADAPTER), true);
		map.makeUnmodifiable();
		return map;
	}

	@SuppressWarnings("unchecked")
	private static ParameterizedTypeHandlerMap<InstanceCreator<?>> createDefaultInstanceCreators() {
		ParameterizedTypeHandlerMap<InstanceCreator<?>> map = new ParameterizedTypeHandlerMap<InstanceCreator<?>>();
		DefaultConstructorAllocator allocator = new DefaultConstructorAllocator(
				50);

		// Map Instance Creators
		map.registerForTypeHierarchy(Map.class,
				new DefaultConstructorCreator<Map>(LinkedHashMap.class,
						allocator), true);

		// Add Collection type instance creators
		DefaultConstructorCreator<List> listCreator = new DefaultConstructorCreator<List>(
				ArrayList.class, allocator);
		DefaultConstructorCreator<Queue> queueCreator = new DefaultConstructorCreator<Queue>(
				LinkedList.class, allocator);
		DefaultConstructorCreator<Set> setCreator = new DefaultConstructorCreator<Set>(
				HashSet.class, allocator);
		DefaultConstructorCreator<SortedSet> sortedSetCreator = new DefaultConstructorCreator<SortedSet>(
				TreeSet.class, allocator);
		map.registerForTypeHierarchy(Collection.class, listCreator, true);
		map.registerForTypeHierarchy(Queue.class, queueCreator, true);
		map.registerForTypeHierarchy(Set.class, setCreator, true);
		map.registerForTypeHierarchy(SortedSet.class, sortedSetCreator, true);

		map.makeUnmodifiable();
		return map;
	}

	@SuppressWarnings("unchecked")
	private static JsonDeserializer<?> wrapDeserializer(
			JsonDeserializer<?> deserializer) {
		return new JsonDeserializerExceptionWrapper(deserializer);
	}

	static ParameterizedTypeHandlerMap<JsonSerializer<?>> getDefaultSerializers() {
		return getDefaultSerializers(false, LongSerializationPolicy.DEFAULT);
	}

	static ParameterizedTypeHandlerMap<JsonSerializer<?>> getAllDefaultSerializers() {
		ParameterizedTypeHandlerMap<JsonSerializer<?>> defaultSerializers = getDefaultSerializers(
				false, LongSerializationPolicy.DEFAULT);
		defaultSerializers.register(DEFAULT_HIERARCHY_SERIALIZERS);
		return defaultSerializers;
	}

	static ParameterizedTypeHandlerMap<JsonDeserializer<?>> getAllDefaultDeserializers() {
		ParameterizedTypeHandlerMap<JsonDeserializer<?>> defaultDeserializers = getDefaultDeserializers()
				.copyOf();
		defaultDeserializers.register(DEFAULT_HIERARCHY_DESERIALIZERS);
		return defaultDeserializers;
	}

	static ParameterizedTypeHandlerMap<JsonSerializer<?>> getDefaultSerializers(
			boolean serializeSpecialFloatingPointValues,
			LongSerializationPolicy longSerializationPolicy) {
		ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers = new ParameterizedTypeHandlerMap<JsonSerializer<?>>();

		// Double primitive
		DefaultTypeAdapters.DoubleSerializer doubleSerializer = new DefaultTypeAdapters.DoubleSerializer(
				serializeSpecialFloatingPointValues);
		serializers.registerIfAbsent(Double.class, doubleSerializer);
		serializers.registerIfAbsent(double.class, doubleSerializer);

		// Float primitive
		DefaultTypeAdapters.FloatSerializer floatSerializer = new DefaultTypeAdapters.FloatSerializer(
				serializeSpecialFloatingPointValues);
		serializers.registerIfAbsent(Float.class, floatSerializer);
		serializers.registerIfAbsent(float.class, floatSerializer);

		// Long primitive
		DefaultTypeAdapters.LongSerializer longSerializer = new DefaultTypeAdapters.LongSerializer(
				longSerializationPolicy);
		serializers.registerIfAbsent(Long.class, longSerializer);
		serializers.registerIfAbsent(long.class, longSerializer);

		serializers.registerIfAbsent(DEFAULT_SERIALIZERS);
		return serializers;
	}

	static ParameterizedTypeHandlerMap<JsonDeserializer<?>> getDefaultDeserializers() {
		return DEFAULT_DESERIALIZERS;
	}

	static ParameterizedTypeHandlerMap<InstanceCreator<?>> getDefaultInstanceCreators() {
		return DEFAULT_INSTANCE_CREATORS;
	}

	/**
	 * This type adapter supports three subclasses of date: Date, Timestamp, and
	 * java.sql.Date.
	 */
	static final class DefaultDateTypeAdapter implements JsonSerializer<Date>,
			JsonDeserializer<Date> {
		private final DateFormat enUsFormat;
		private final DateFormat localFormat;
		private final DateFormat iso8601Format;

		DefaultDateTypeAdapter() {
			this(DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
					DateFormat.DEFAULT, Locale.US),
					DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
							DateFormat.DEFAULT));
		}

		DefaultDateTypeAdapter(String datePattern) {
			this(new SimpleDateFormat(datePattern, Locale.US),
					new SimpleDateFormat(datePattern));
		}

		DefaultDateTypeAdapter(int style) {
			this(DateFormat.getDateInstance(style, Locale.US), DateFormat
					.getDateInstance(style));
		}

		public DefaultDateTypeAdapter(int dateStyle, int timeStyle) {
			this(DateFormat
					.getDateTimeInstance(dateStyle, timeStyle, Locale.US),
					DateFormat.getDateTimeInstance(dateStyle, timeStyle));
		}

		DefaultDateTypeAdapter(DateFormat enUsFormat, DateFormat localFormat) {
			this.enUsFormat = enUsFormat;
			this.localFormat = localFormat;
			this.iso8601Format = new SimpleDateFormat(
					"yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
			this.iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
		}

		// These methods need to be synchronized since JDK DateFormat classes
		// are not thread-safe
		// See issue 162
		public JsonElement serialize(Date src, Type typeOfSrc,
				JsonSerializationContext context) {
			synchronized (localFormat) {
				String dateFormatAsString = enUsFormat.format(src);
				return new JsonPrimitive(dateFormatAsString);
			}
		}

		public Date deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			if (!(json instanceof JsonPrimitive)) {
				throw new JsonParseException(
						"The date should be a string value");
			}
			Date date = deserializeToDate(json);
			if (typeOfT == Date.class) {
				return date;
			} else if (typeOfT == Timestamp.class) {
				return new Timestamp(date.getTime());
			} else if (typeOfT == java.sql.Date.class) {
				return new java.sql.Date(date.getTime());
			} else {
				throw new IllegalArgumentException(getClass()
						+ " cannot deserialize to " + typeOfT);
			}
		}

		private Date deserializeToDate(JsonElement json) {
			synchronized (localFormat) {
				try {
					return localFormat.parse(json.getAsString());
				} catch (ParseException ignored) {
				}
				try {
					return enUsFormat.parse(json.getAsString());
				} catch (ParseException ignored) {
				}
				try {
					return iso8601Format.parse(json.getAsString());
				} catch (ParseException e) {
					throw new JsonSyntaxException(json.getAsString(), e);
				}
			}
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(DefaultDateTypeAdapter.class.getSimpleName());
			sb.append('(').append(localFormat.getClass().getSimpleName())
					.append(')');
			return sb.toString();
		}
	}

	static final class DefaultJavaSqlDateTypeAdapter implements
			JsonSerializer<java.sql.Date>, JsonDeserializer<java.sql.Date> {
		private final DateFormat format;

		DefaultJavaSqlDateTypeAdapter() {
			this.format = new SimpleDateFormat("MMM d, yyyy");
		}

		public JsonElement serialize(java.sql.Date src, Type typeOfSrc,
				JsonSerializationContext context) {
			synchronized (format) {
				String dateFormatAsString = format.format(src);
				return new JsonPrimitive(dateFormatAsString);
			}
		}

		public java.sql.Date deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			if (!(json instanceof JsonPrimitive)) {
				throw new JsonParseException(
						"The date should be a string value");
			}
			try {
				synchronized (format) {
					Date date = format.parse(json.getAsString());
					return new java.sql.Date(date.getTime());
				}
			} catch (ParseException e) {
				throw new JsonSyntaxException(e);
			}
		}
	}

	static final class DefaultTimestampDeserializer implements
			JsonDeserializer<Timestamp> {
		public Timestamp deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			Date date = context.deserialize(json, Date.class);
			return new Timestamp(date.getTime());
		}
	}

	static final class DefaultTimeTypeAdapter implements JsonSerializer<Time>,
			JsonDeserializer<Time> {
		private final DateFormat format;

		DefaultTimeTypeAdapter() {
			this.format = new SimpleDateFormat("hh:mm:ss a");
		}

		public JsonElement serialize(Time src, Type typeOfSrc,
				JsonSerializationContext context) {
			synchronized (format) {
				String dateFormatAsString = format.format(src);
				return new JsonPrimitive(dateFormatAsString);
			}
		}

		public Time deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			if (!(json instanceof JsonPrimitive)) {
				throw new JsonParseException(
						"The date should be a string value");
			}
			try {
				synchronized (format) {
					Date date = format.parse(json.getAsString());
					return new Time(date.getTime());
				}
			} catch (ParseException e) {
				throw new JsonSyntaxException(e);
			}
		}
	}

	private static final class GregorianCalendarTypeAdapter implements
			JsonSerializer<GregorianCalendar>,
			JsonDeserializer<GregorianCalendar> {

		private static final String YEAR = "year";
		private static final String MONTH = "month";
		private static final String DAY_OF_MONTH = "dayOfMonth";
		private static final String HOUR_OF_DAY = "hourOfDay";
		private static final String MINUTE = "minute";
		private static final String SECOND = "second";

		public JsonElement serialize(GregorianCalendar src, Type typeOfSrc,
				JsonSerializationContext context) {
			JsonObject obj = new JsonObject();
			obj.addProperty(YEAR, src.get(Calendar.YEAR));
			obj.addProperty(MONTH, src.get(Calendar.MONTH));
			obj.addProperty(DAY_OF_MONTH, src.get(Calendar.DAY_OF_MONTH));
			obj.addProperty(HOUR_OF_DAY, src.get(Calendar.HOUR_OF_DAY));
			obj.addProperty(MINUTE, src.get(Calendar.MINUTE));
			obj.addProperty(SECOND, src.get(Calendar.SECOND));
			return obj;
		}

		public GregorianCalendar deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			JsonObject obj = json.getAsJsonObject();
			int year = obj.get(YEAR).getAsInt();
			int month = obj.get(MONTH).getAsInt();
			int dayOfMonth = obj.get(DAY_OF_MONTH).getAsInt();
			int hourOfDay = obj.get(HOUR_OF_DAY).getAsInt();
			int minute = obj.get(MINUTE).getAsInt();
			int second = obj.get(SECOND).getAsInt();
			return new GregorianCalendar(year, month, dayOfMonth, hourOfDay,
					minute, second);
		}

		@Override
		public String toString() {
			return GregorianCalendarTypeAdapter.class.getSimpleName();
		}
	}

	static final class DefaultInetAddressAdapter implements
			JsonDeserializer<InetAddress>, JsonSerializer<InetAddress> {

		public InetAddress deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return InetAddress.getByName(json.getAsString());
			} catch (UnknownHostException e) {
				throw new JsonParseException(e);
			}
		}

		public JsonElement serialize(InetAddress src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.getHostAddress());
		}
	}

	@SuppressWarnings("unchecked")
	private static final class EnumTypeAdapter<T extends Enum<T>> implements
			JsonSerializer<T>, JsonDeserializer<T> {
		public JsonElement serialize(T src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.name());
		}

		@SuppressWarnings("cast")
		public T deserialize(JsonElement json, Type classOfT,
				JsonDeserializationContext context) throws JsonParseException {
			return (T) Enum.valueOf((Class<T>) classOfT, json.getAsString());
		}

		@Override
		public String toString() {
			return EnumTypeAdapter.class.getSimpleName();
		}
	}

	private static final class BitSetTypeAdapter implements
			JsonSerializer<BitSet>, JsonDeserializer<BitSet> {
		public JsonElement serialize(BitSet src, Type typeOfSrc,
				JsonSerializationContext context) {
			JsonArray array = new JsonArray();
			for (int i = 0; i < src.length(); i++) {
				int value = (src.get(i)) ? 1 : 0;
				array.add(new JsonPrimitive(value));
			}
			return array;
		}

		public BitSet deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			if (!json.isJsonArray()) {
				throw new JsonParseException("Expected an array of bits.");
			}
			BitSet result = new BitSet();
			JsonArray array = json.getAsJsonArray();
			for (int i = 0; i < array.size(); i++) {
				JsonElement element = array.get(i);
				if (element.getAsBoolean()) {
					result.set(i);
				}
			}
			return result;
		}

		@Override
		public String toString() {
			return BitSetTypeAdapter.class.getSimpleName();
		}
	}

	private static final class UrlTypeAdapter implements JsonSerializer<URL>,
			JsonDeserializer<URL> {
		public JsonElement serialize(URL src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.toExternalForm());
		}

		public URL deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return new URL(json.getAsString());
			} catch (MalformedURLException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return UrlTypeAdapter.class.getSimpleName();
		}
	}

	private static final class UriTypeAdapter implements JsonSerializer<URI>,
			JsonDeserializer<URI> {
		public JsonElement serialize(URI src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.toASCIIString());
		}

		public URI deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return new URI(json.getAsString());
			} catch (URISyntaxException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return UriTypeAdapter.class.getSimpleName();
		}
	}

	private static final class UuidTypeAdapter implements JsonSerializer<UUID>,
			JsonDeserializer<UUID> {
		public JsonElement serialize(UUID src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.toString());
		}

		public UUID deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			return UUID.fromString(json.getAsString());
		}

		@Override
		public String toString() {
			return UuidTypeAdapter.class.getSimpleName();
		}
	}

	private static final class LocaleTypeAdapter implements
			JsonSerializer<Locale>, JsonDeserializer<Locale> {
		public JsonElement serialize(Locale src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.toString());
		}

		public Locale deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			String locale = json.getAsString();
			StringTokenizer tokenizer = new StringTokenizer(locale, "_");
			String language = null;
			String country = null;
			String variant = null;
			if (tokenizer.hasMoreElements()) {
				language = tokenizer.nextToken();
			}
			if (tokenizer.hasMoreElements()) {
				country = tokenizer.nextToken();
			}
			if (tokenizer.hasMoreElements()) {
				variant = tokenizer.nextToken();
			}
			if (country == null && variant == null) {
				return new Locale(language);
			} else if (variant == null) {
				return new Locale(language, country);
			} else {
				return new Locale(language, country, variant);
			}
		}

		@Override
		public String toString() {
			return LocaleTypeAdapter.class.getSimpleName();
		}
	}

	@SuppressWarnings("unchecked")
	private static final class CollectionTypeAdapter implements
			JsonSerializer<Collection>, JsonDeserializer<Collection> {
		public JsonElement serialize(Collection src, Type typeOfSrc,
				JsonSerializationContext context) {
			if (src == null) {
				return JsonNull.INSTANCE;
			}
			JsonArray array = new JsonArray();
			Type childGenericType = null;
			if (typeOfSrc instanceof ParameterizedType) {
				Class<?> rawTypeOfSrc = $Gson$Types.getRawType(typeOfSrc);
				childGenericType = $Gson$Types.getCollectionElementType(
						typeOfSrc, rawTypeOfSrc);
			}
			for (Object child : src) {
				if (child == null) {
					array.add(JsonNull.INSTANCE);
				} else {
					Type childType = (childGenericType == null || childGenericType == Object.class) ? child
							.getClass() : childGenericType;
					JsonElement element = context.serialize(child, childType,
							false, false);
					array.add(element);
				}
			}
			return array;
		}

		public Collection deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			if (json.isJsonNull()) {
				return null;
			}
			// Use ObjectConstructor to create instance instead of hard-coding a
			// specific type.
			// This handles cases where users are using their own subclass of
			// Collection.
			Collection collection = constructCollectionType(typeOfT, context);
			Type childType = $Gson$Types.getCollectionElementType(typeOfT,
					$Gson$Types.getRawType(typeOfT));
			for (JsonElement childElement : json.getAsJsonArray()) {
				if (childElement == null || childElement.isJsonNull()) {
					collection.add(null);
				} else {
					Object value = context.deserialize(childElement, childType);
					collection.add(value);
				}
			}
			return collection;
		}

		private Collection constructCollectionType(Type collectionType,
				JsonDeserializationContext context) {
			return context.construct(collectionType);
		}
	}

	private static final class BigDecimalTypeAdapter implements
			JsonSerializer<BigDecimal>, JsonDeserializer<BigDecimal> {
		public JsonElement serialize(BigDecimal src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public BigDecimal deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsBigDecimal();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return BigDecimalTypeAdapter.class.getSimpleName();
		}
	}

	private static final class BigIntegerTypeAdapter implements
			JsonSerializer<BigInteger>, JsonDeserializer<BigInteger> {

		public JsonElement serialize(BigInteger src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public BigInteger deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsBigInteger();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return BigIntegerTypeAdapter.class.getSimpleName();
		}
	}

	private static final class NumberTypeAdapter implements
			JsonSerializer<Number>, JsonDeserializer<Number> {
		public JsonElement serialize(Number src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public Number deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsNumber();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return NumberTypeAdapter.class.getSimpleName();
		}
	}

	private static final class LongSerializer implements JsonSerializer<Long> {
		private final LongSerializationPolicy longSerializationPolicy;

		private LongSerializer(LongSerializationPolicy longSerializationPolicy) {
			this.longSerializationPolicy = longSerializationPolicy;
		}

		public JsonElement serialize(Long src, Type typeOfSrc,
				JsonSerializationContext context) {
			return longSerializationPolicy.serialize(src);
		}

		@Override
		public String toString() {
			return LongSerializer.class.getSimpleName();
		}
	}

	private static final class LongDeserializer implements
			JsonDeserializer<Long> {
		public Long deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsLong();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return LongDeserializer.class.getSimpleName();
		}
	}

	private static final class IntegerTypeAdapter implements
			JsonSerializer<Integer>, JsonDeserializer<Integer> {
		public JsonElement serialize(Integer src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public Integer deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsInt();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return IntegerTypeAdapter.class.getSimpleName();
		}
	}

	private static final class ShortTypeAdapter implements
			JsonSerializer<Short>, JsonDeserializer<Short> {
		public JsonElement serialize(Short src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public Short deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsShort();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return ShortTypeAdapter.class.getSimpleName();
		}
	}

	private static final class ByteTypeAdapter implements JsonSerializer<Byte>,
			JsonDeserializer<Byte> {
		public JsonElement serialize(Byte src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public Byte deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsByte();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return ByteTypeAdapter.class.getSimpleName();
		}
	}

	static final class FloatSerializer implements JsonSerializer<Float> {
		private final boolean serializeSpecialFloatingPointValues;

		FloatSerializer(boolean serializeSpecialDoubleValues) {
			this.serializeSpecialFloatingPointValues = serializeSpecialDoubleValues;
		}

		public JsonElement serialize(Float src, Type typeOfSrc,
				JsonSerializationContext context) {
			if (!serializeSpecialFloatingPointValues) {
				if (Float.isNaN(src) || Float.isInfinite(src)) {
					throw new IllegalArgumentException(
							src
									+ " is not a valid float value as per JSON specification. To override this"
									+ " behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method.");
				}
			}
			return new JsonPrimitive(src);
		}
	}

	private static final class FloatDeserializer implements
			JsonDeserializer<Float> {
		public Float deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsFloat();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return FloatDeserializer.class.getSimpleName();
		}
	}

	static final class DoubleSerializer implements JsonSerializer<Double> {
		private final boolean serializeSpecialFloatingPointValues;

		DoubleSerializer(boolean serializeSpecialDoubleValues) {
			this.serializeSpecialFloatingPointValues = serializeSpecialDoubleValues;
		}

		public JsonElement serialize(Double src, Type typeOfSrc,
				JsonSerializationContext context) {
			if (!serializeSpecialFloatingPointValues) {
				if (Double.isNaN(src) || Double.isInfinite(src)) {
					throw new IllegalArgumentException(
							src
									+ " is not a valid double value as per JSON specification. To override this"
									+ " behavior, use GsonBuilder.serializeSpecialDoubleValues() method.");
				}
			}
			return new JsonPrimitive(src);
		}
	}

	private static final class DoubleDeserializer implements
			JsonDeserializer<Double> {
		public Double deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsDouble();
			} catch (NumberFormatException e) {
				throw new JsonSyntaxException(e);
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return DoubleDeserializer.class.getSimpleName();
		}
	}

	private static final class CharacterTypeAdapter implements
			JsonSerializer<Character>, JsonDeserializer<Character> {
		public JsonElement serialize(Character src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public Character deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			return json.getAsCharacter();
		}

		@Override
		public String toString() {
			return CharacterTypeAdapter.class.getSimpleName();
		}
	}

	private static final class StringTypeAdapter implements
			JsonSerializer<String>, JsonDeserializer<String> {
		public JsonElement serialize(String src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public String deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			return json.getAsString();
		}

		@Override
		public String toString() {
			return StringTypeAdapter.class.getSimpleName();
		}
	}

	private static final class StringBuilderTypeAdapter implements
			JsonSerializer<StringBuilder>, JsonDeserializer<StringBuilder> {
		public JsonElement serialize(StringBuilder src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.toString());
		}

		public StringBuilder deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			return new StringBuilder(json.getAsString());
		}

		@Override
		public String toString() {
			return StringBuilderTypeAdapter.class.getSimpleName();
		}
	}

	private static final class StringBufferTypeAdapter implements
			JsonSerializer<StringBuffer>, JsonDeserializer<StringBuffer> {
		public JsonElement serialize(StringBuffer src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.toString());
		}

		public StringBuffer deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			return new StringBuffer(json.getAsString());
		}

		@Override
		public String toString() {
			return StringBufferTypeAdapter.class.getSimpleName();
		}
	}

	private static final class BooleanTypeAdapter implements
			JsonSerializer<Boolean>, JsonDeserializer<Boolean> {
		public JsonElement serialize(Boolean src, Type typeOfSrc,
				JsonSerializationContext context) {
			return new JsonPrimitive(src);
		}

		public Boolean deserialize(JsonElement json, Type typeOfT,
				JsonDeserializationContext context) throws JsonParseException {
			try {
				return json.getAsBoolean();
			} catch (UnsupportedOperationException e) {
				throw new JsonSyntaxException(e);
			} catch (IllegalStateException e) {
				throw new JsonSyntaxException(e);
			}
		}

		@Override
		public String toString() {
			return BooleanTypeAdapter.class.getSimpleName();
		}
	}

	@SuppressWarnings("unchecked")
	private static final class DefaultConstructorCreator<T> implements
			InstanceCreator<T> {
		private final Class<? extends T> defaultInstance;
		private final DefaultConstructorAllocator allocator;

		public DefaultConstructorCreator(Class<? extends T> defaultInstance,
				DefaultConstructorAllocator allocator) {
			this.defaultInstance = defaultInstance;
			this.allocator = allocator;
		}

		public T createInstance(Type type) {
			Class<?> rawType = $Gson$Types.getRawType(type);
			try {
				T specificInstance = (T) allocator.newInstance(rawType);
				return (specificInstance == null) ? allocator
						.newInstance(defaultInstance) : specificInstance;
			} catch (Exception e) {
				throw new JsonIOException(e);
			}
		}

		@Override
		public String toString() {
			return DefaultConstructorCreator.class.getSimpleName();
		}
	}
}
